package org.fujene.diskblock.repli;

import static org.fujene.diskblock.repli.ReplicateDictionaryDiskBlock.Type.*;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;

import org.fujene.constants.Constants;
import org.fujene.diskblock.DiskBlock;
import org.fujene.memoryblock.MemoryBlock;
import org.fujene.memoryblock.repli.ReplicateDictionaryBlock;
import org.fujene.structure.ExtendTerm;
import org.fujene.structure.LookupPointer;
import org.fujene.toolbox.ByteConverter;

public class ReplicateDictionaryDiskBlock extends DiskBlock{

	public ReplicateDictionaryDiskBlock(File fileDesc) throws Exception{
		super(fileDesc);
	}

	public ReplicateDictionaryDiskBlock(ReplicateDictionaryBlock memBlock, String dir){
		super(memBlock, dir);
	}

	public enum Type{
		ALL, NEXT, DOCID, INVINDEX, STR, DOCFRQ,
	}

	private class DictionaryTerm{
		private LookupPointer nextTerm;
		private int docfreq;
		private LookupPointer[] invIndex;
		private long[] firstID;
		private int strlen;
		private String str;
		private int opt_type;

		// データ読み込み用
		public DictionaryTerm(int from, Type type) throws Exception{
			opt_type = getOption(1);

			// Next pointer (6B)
			if(type == ALL || type == NEXT) this.nextTerm = LookupPointer.fromBytes(getBody(from, PTR));
			else this.nextTerm = LookupPointer.EMPTY;
			from += PTR;

			// Doc. frequency (3B)
			if(type == ALL || type == DOCFRQ) this.docfreq = ByteConverter.byteToInt(getBody(from, DOCFREQ));
			else this.nextTerm = LookupPointer.EMPTY;
			from += DOCFREQ;

			// Invert index, first ID (x14B)
			if(type == ALL || type == INVINDEX || type == DOCID){
				this.invIndex = new LookupPointer[opt_type];
				this.firstID = new long[opt_type];
				for(int i = 0; i < opt_type; i++){
					this.invIndex[i] = LookupPointer.fromBytes(getBody(from, PTR));
					from += PTR;
					this.firstID[i] = ByteConverter.byteToLong(getBody(from, 8));
					from += 8;
				}
			}

			// Term (2+B)
			if(type == ALL || type == STR){
				this.strlen = ByteConverter.byteToInt(getBody(from, STRLEN));
				from += STRLEN;
				try{
					this.str = new String(getBody(from, strlen), "UTF-8");
				}catch(UnsupportedEncodingException e){}
			}
		}
	}

	public LookupPointer getDocIDPointer(int index, int ptr) throws Exception{
		DictionaryTerm ptredDic = new DictionaryTerm(ptr, Type.DOCID);

		if(ptredDic.invIndex[index].equals(LookupPointer.EMPTY)) return null;
		else return ptredDic.invIndex[index];
	}

	public long getFirstPointer(int index, int ptr) throws Exception{
		DictionaryTerm ptredDic = new DictionaryTerm(ptr, Type.DOCID);

		return ptredDic.firstID[index];
	}

	public ExtendTerm getTerm(int ptr, int indexType) throws Exception{
		DictionaryTerm ptredDic = new DictionaryTerm(ptr, ALL);

		if(ptredDic.nextTerm.equals(LookupPointer.EMPTY)) ptredDic.nextTerm = null;
		if(ptredDic.invIndex[indexType].equals(LookupPointer.EMPTY)) ptredDic.invIndex[indexType] = null;

		return new ExtendTerm(ptredDic.str, ptredDic.nextTerm, null, new LookupPointer(this.getID(), ptr),
				-1, ptredDic.invIndex, ptredDic.firstID, ptredDic.docfreq);
	}

	public int getTermCount() throws Exception{
		return this.getOption(ReplicateDictionaryBlock.OPT_LEN);
	}

	public LookupPointer getNextPointer(int ptr) throws Exception{
		DictionaryTerm ptredDic = new DictionaryTerm(ptr, NEXT);

		if(ptredDic.nextTerm.equals(LookupPointer.EMPTY)) return null;
		else return ptredDic.nextTerm;
	}

	public LookupPointer getInvIndexPointer(int ptr, int index) throws Exception{
		DictionaryTerm ptredDic = new DictionaryTerm(ptr, INVINDEX);

		if(ptredDic.invIndex[index].equals(LookupPointer.EMPTY)) return null;
		else{
			LookupPointer retPtr = ptredDic.invIndex[index];
			retPtr.firstID = ptredDic.firstID[index];
			return ptredDic.invIndex[index];
		}
	}

	public long getFirstID(int ptr, int index) throws Exception{
		DictionaryTerm ptredDic = new DictionaryTerm(ptr, DOCID);

		return ptredDic.firstID[index];
	}

	// Modify process, using random access file
	public void setNextPointer(int ptr, LookupPointer next){
		try{
			RandomAccessFile randomFile = new RandomAccessFile(fileDesc, "rw");

			// For back-compatibility
			switch(version){
			case Constants.FINALVER:
				randomFile.seek((4 * HEADERLEN) + (4 * optionSize) + ptr + PTR);
				randomFile.write(next.toBytes());
			}
			randomFile.close();
		}catch(FileNotFoundException e){}catch(IOException e){}
	}

	public void incrementDocfreq(int ptr){
		try{
			RandomAccessFile randomFile = new RandomAccessFile(fileDesc, "rw");

			// For back-compatibility
			switch(version){
			case Constants.FINALVER:
				randomFile.seek((4 * HEADERLEN) + (4 * optionSize) + ptr);
				byte[] docb = new byte[DOCFREQ];
				randomFile.read(docb);
				randomFile.seek(randomFile.getFilePointer()-DOCFREQ);
				randomFile.write(ByteConverter.intToByte(ByteConverter.byteToInt(docb) + 1, DOCFREQ));
			}
			randomFile.close();
		}catch(FileNotFoundException e){}catch(IOException e){}
	}

	public void setInvIndex(int ptr, LookupPointer docIDptr, long firstID, int indexType){
		try{
			RandomAccessFile randomFile = new RandomAccessFile(fileDesc, "rw");

			// For back-compatibility
			switch(version){
			case Constants.FINALVER:
				randomFile.seek((4 * HEADERLEN) + (4 * optionSize) + ptr + PTR + DOCFREQ
						+ (indexType * (PTR + 8)));
				randomFile.write(docIDptr.toBytes());
				randomFile.write(ByteConverter.longToByte(firstID, 8));
			}
			randomFile.close();
		}catch(FileNotFoundException e){}catch(IOException e){}
	}

	@Override
	public int toMemory(MemoryBlock memBlock){
		if(memBlock instanceof ReplicateDictionaryBlock){
			try{
				for(int i = 0; i < ReplicateDictionaryBlock.OPTSIZE; i++)
					memBlock.setOption(i, this.getOption(i));
				memBlock.setBody(this.getBody(0, this.bodySize), 0);
				memBlock.setBodyVacancy(this.bodyVacancy);
			}catch(Exception e){}
		}
		return this.bodySize - this.bodyVacancy;
	}

	@Override
	public byte[] getData(){
		throw new NoSuchMethodError("Unimplemented method");
	}
}
